﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;

namespace NetfractionHub.Network.Protocol
{
	public class ProtocolMessage
	{
		public enum MessageTypeEnum
		{
			Unknown,    // this shouldn't happen, but treat it like an Info message - it's the safest one
			Broadcast,
			Client,
			Direct,
			Echo,
			Feature,
			Hub,
			Info,
			Udp
		}

		private MessageTypeEnum _messageType;
		private string _messageCode;
		private List<ProtocolParameter> _parameters;

		private string _sessionId;
		private string _clientId;
		private string _targetSessionId;

		public string TargetSessionId
		{
			get { return _targetSessionId; }
			set { _targetSessionId = value; }
		}

		public string SessionId
		{
			get { return _sessionId; }
			set { _sessionId = value; }
		}

		public ProtocolMessage()
		{
			_messageType = MessageTypeEnum.Hub;
			_messageCode = "";
			_parameters = new List<ProtocolParameter>();
		}

		public ProtocolMessage(MessageTypeEnum messageType, string messageCode)
			: this()
		{
			_messageType = messageType;
			_messageCode = messageCode;
		}

		public ProtocolMessage(MessageTypeEnum messageType, string messageCode, string sid)
			: this()
		{
			_messageType = messageType;
			_messageCode = messageCode;
			_sessionId = sid;
		}

		public ProtocolMessage(MessageTypeEnum messageType, string messageCode, string sid, string targetSid)
			: this()
		{
			_messageType = messageType;
			_messageCode = messageCode;
			_sessionId = sid;
			_targetSessionId = targetSid;
		}

		public ProtocolMessage(string message)
		{
			Parse(message);
		}

		/// <summary>
		/// Decode a UTF-8 encoded message into a string
		/// </summary>
		public static string DecodeMessage(byte[] buffer, int offset, int length)
		{
			return Encoding.UTF8.GetString(buffer, offset, length);
		}

		/// <summary>
		/// Decode a UTF-8 encoded message into a string
		/// </summary>
		public static string DecodeMessage(byte[] buffer, int length)
		{
			return DecodeMessage(buffer, 0, length);
		}

		/// <summary>
		/// Encode a message into UTF-8
		/// </summary>
		public static byte[] EncodeMessage(string message)
		{
			return System.Text.Encoding.UTF8.GetBytes(message.Trim() + "\n");
		}

		public static List<ProtocolMessage> ProcessIncomingMessages(string messages)
		{
			List<ProtocolMessage> protocolMessageList = new List<ProtocolMessage>();

			ProtocolMessage proto;
			foreach (string msg in messages.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries))
			{
				if (!string.IsNullOrEmpty(msg))
				{
					proto = new ProtocolMessage();
					proto.Parse(msg);
					protocolMessageList.Add(proto);
				}
			}

			return protocolMessageList;
		}

		public void Parse(string message)
		{
			message = message.Trim();

			if (!string.IsNullOrEmpty(message))
			{
				_messageType = GetMessageTypeFromChar(message[0]);
				_messageCode = message.Substring(1, 3);

				string[] data = message.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
				_parameters = new List<ProtocolParameter>(data.Length);

				// location parameters begin
				int dataStart = 1;

				// for C, I and H messages, there is no SID or CID passed.
				// a B message begins with a SID
				if (_messageType == MessageTypeEnum.Broadcast || _messageType == MessageTypeEnum.Direct ||
					_messageType == MessageTypeEnum.Echo || _messageType == MessageTypeEnum.Feature)
				{
					_sessionId = data[dataStart];
					dataStart++;
				}
				
				if (_messageType == MessageTypeEnum.Direct || _messageType == MessageTypeEnum.Echo) // a D and E message begin with a Local SID then a Target SID
				{
					_targetSessionId = data[dataStart];
					dataStart++;
				}
				else if (_messageType == MessageTypeEnum.Udp) // a U message begins with a CID
				{
					_clientId = data[dataStart];
				}

				for (int i = dataStart; i < data.Length; i++)
				{
					_parameters.Add(new ProtocolParameter(data[i]));
				}
			}
		}

		private static MessageTypeEnum GetMessageTypeFromChar(char messageType)
		{
			try
			{
				switch (messageType)
				{
					case 'B':
						return MessageTypeEnum.Broadcast;
					case 'C':
						return MessageTypeEnum.Client;
					case 'D':
						return MessageTypeEnum.Direct;
					case 'E':
						return MessageTypeEnum.Echo;
					case 'F':
						return MessageTypeEnum.Feature;
					case 'H':
						return MessageTypeEnum.Hub;
					case 'I':
						return MessageTypeEnum.Info;
					case 'U':
						return MessageTypeEnum.Udp;
					default:
						throw new ArgumentException("MessageType is invalid in GetMessageTypeFromChar");
				}
			}
			catch (ArgumentException)
			{
				return MessageTypeEnum.Info;
			}
		}

		private string GetProtocolMessageCharByType(MessageTypeEnum type)
		{
			switch (type)
			{
				case MessageTypeEnum.Unknown:
					return "I";         // shouldn't happen!
				case MessageTypeEnum.Broadcast:
					return "B";
				case MessageTypeEnum.Client:
					return "C";
				case MessageTypeEnum.Direct:
					return "D";
				case MessageTypeEnum.Echo:
					return "E";
				case MessageTypeEnum.Feature:
					return "F";
				case MessageTypeEnum.Hub:
					return "H";
				case MessageTypeEnum.Info:
					return "I";
				case MessageTypeEnum.Udp:
					return "U";
				default:
					throw new ArgumentException("Invalid protocol message character. Was not B, C, D, E, F, H, I, or U.");
			}
		}

		/// <summary>
		/// Overrides the ToString method
		/// </summary>
		public override string ToString()
		{
			StringBuilder sb = new StringBuilder(GetProtocolMessageCharByType(_messageType));
			sb.Append(_messageCode);

			// Check message type
			if (_messageType == MessageTypeEnum.Broadcast || _messageType == MessageTypeEnum.Feature)
			{
				sb.Append(' ');
				sb.Append(_sessionId);
			}
			else if (_messageType == MessageTypeEnum.Direct || _messageType == MessageTypeEnum.Echo)
			{
				sb.Append(' ');
				sb.Append(_sessionId);
				sb.Append(' ');
				sb.Append(_targetSessionId);
			}
			else if (_messageType == MessageTypeEnum.Udp)
			{
				sb.Append(' ');
				sb.Append(_clientId);
			}

			for (int i = 0; i < _parameters.Count; i++)
			{
				// the ProtocolParameter's ToString override will escape the parameter for us
				sb.Append(' ');
				sb.Append(_parameters[i].ToString());
			}

			return sb.ToString();
		}

		public List<ProtocolParameter> Parameters
		{
			get { return _parameters; }
			set { _parameters = value; }
		}

		public MessageTypeEnum MessageType
		{
			get { return _messageType; }
			set { _messageType = value; }
		}

		public string MessageCode
		{
			get { return _messageCode; }
			set { _messageCode = value; }
		}
	}
}
